/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <mc/dict.h>
#include <mc/variant.h>
#include <mc/log.h>
#include <mctp/mctp.h>
#include <algorithm>
#include <cstring>
#include <cstddef>
#include "pldm_over_mctp_standard.h"

namespace dev {

mc::dict get_pldm_command_ctx(uint8_t pldm_type, uint8_t command_code) {
    return mc::dict({
        {"PldmType", pldm_type},
        {"CommandCode", command_code}
    });
}

std::vector<uint8_t> pack_sensor_reading_request(uint16_t sensor_id, bool rearm_event_state) {
    return std::vector<uint8_t>{
        static_cast<uint8_t>(sensor_id & 0xFF),
        static_cast<uint8_t>((sensor_id >> 8) & 0xFF),
        static_cast<uint8_t>(rearm_event_state ? 0x01 : 0x00)
    };
}

std::vector<uint8_t> pack_state_sensor_readings_request(uint16_t sensor_id, uint8_t sensor_rearm) {
    return std::vector<uint8_t>{
        static_cast<uint8_t>(sensor_id & 0xFF),
        static_cast<uint8_t>((sensor_id >> 8) & 0xFF),
        sensor_rearm,
        0x00  // reserved
    };
}

bool pldm_over_mctp_standard::get_sensor_reading(uint16_t sensor_id, bool rearm_event_state, NumericSensorReading& result) {
    mc::dict ctx = get_pldm_command_ctx(MCTP_PLDM_TYPE_2, PLDM_GET_SENSOR_READING);
    std::vector<uint8_t> data = pack_sensor_reading_request(sensor_id, rearm_event_state);
    std::string rsp_str;
    
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        ilog("Unable to get PLDM sensor reading, sensor_id: ${sensor_id}", 
             ("sensor_id", sensor_id));
        return false;
    }

    if (rsp_str.size() < sizeof(pldm_sensor_reading_rsp_t)) {
        ilog("Invalid sensor reading response size: ${size}", ("size", rsp_str.size()));
        return false;
    }

    const pldm_sensor_reading_rsp_t* rsp = reinterpret_cast<const pldm_sensor_reading_rsp_t*>(rsp_str.data());
    

    // result.completion_code = rsp->completion_code;
    result.sensor_data_size = rsp->sensor_data_size;
    result.sensor_operational_state = rsp->sensor_operational_state;
    result.event_message_enable = rsp->event_message_enable;
    result.present_state = rsp->present_state;
    result.previous_state = rsp->previous_state;
    result.event_state = rsp->event_state;

    size_t data_offset = offsetof(pldm_sensor_reading_rsp_t, sensor_data);
    
    // Check if sensor is operational
    if (rsp->sensor_operational_state != MCTP_PLDM_SENSOR_OPERATION_STATE_ENABLE) {
        // Return default value based on data size when sensor is not enabled
        switch (rsp->sensor_data_size) {
            case 0: result.reading = static_cast<uint8_t>(0); break;
            case 1: result.reading = static_cast<int8_t>(0); break;
            case 2: result.reading = static_cast<uint16_t>(0); break;
            case 3: result.reading = static_cast<int16_t>(0); break;
            case 4: result.reading = static_cast<uint32_t>(0); break;
            case 5: result.reading = static_cast<int32_t>(0); break;
            default: 
                result.reading = static_cast<uint8_t>(0);
                break;
        }
        return false;
    }

    // Parse actual sensor data
    switch (rsp->sensor_data_size) {
        case 0: // uint8
            if (rsp_str.size() < data_offset + 1) {
                ilog("Response too small for uint8 data");
                return false;
            }
            result.reading = static_cast<uint8_t>(rsp->sensor_data[0]);
            break;
            
        case 1: // sint8
            if (rsp_str.size() < data_offset + 1) {
                ilog("Response too small for sint8 data");
                return false;
            }
            result.reading = static_cast<int8_t>(rsp->sensor_data[0]);
            break;
            
        case 2: // uint16
            if (rsp_str.size() < data_offset + 2) {
                ilog("Response too small for uint16 data");
                return false;
            }
            {
                uint16_t value;
                std::memcpy(&value, rsp->sensor_data, sizeof(value));
                result.reading = PLDM_LE16_TO_CPU(value);
            }
            break;
            
        case 3: // sint16
            if (rsp_str.size() < data_offset + 2) {
                ilog("Response too small for sint16 data");
                return false;
            }
            {
                uint16_t raw_value;
                std::memcpy(&raw_value, rsp->sensor_data, sizeof(raw_value));
                raw_value = PLDM_LE16_TO_CPU(raw_value);
                int16_t value;
                std::memcpy(&value, &raw_value, sizeof(value));
                result.reading = value;
            }
            break;
            
        case 4: // uint32
            if (rsp_str.size() < data_offset + 4) {
                ilog("Response too small for uint32 data");
                return false;
            }
            {
                uint32_t value;
                std::memcpy(&value, rsp->sensor_data, sizeof(value));
                result.reading = PLDM_LE32_TO_CPU(value);
            }
            break;
            
        case 5: // sint32
            if (rsp_str.size() < data_offset + 4) {
                ilog("Response too small for sint32 data");
                return false;
            }
            {
                uint32_t raw_value;
                std::memcpy(&raw_value, rsp->sensor_data, sizeof(raw_value));
                raw_value = PLDM_LE32_TO_CPU(raw_value);
                int32_t value;
                std::memcpy(&value, &raw_value, sizeof(value));
                result.reading = value;
            }
            break;
            
        default:
            ilog("Unsupported sensor data size: ${size}", ("size", rsp->sensor_data_size));
            return false;
    }

    return true;
}

bool pldm_over_mctp_standard::get_state_sensor_readings(uint16_t sensor_id, uint8_t sensor_rearm, 
                                                        std::vector<pldm_statesensor_field_t>& state_fields) {
    mc::dict ctx = get_pldm_command_ctx(MCTP_PLDM_TYPE_2, PLDM_GET_STATE_SENSOR_READINGS);
    std::vector<uint8_t> data = pack_state_sensor_readings_request(sensor_id, sensor_rearm);
    std::string rsp_str;
    
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        ilog("Unable to get PLDM state sensor readings, sensor_id: ${sensor_id}", 
             ("sensor_id", sensor_id));
        return false;
    }

    if (rsp_str.size() < sizeof(pldm_state_sensor_reading_rsp_t)) {
        ilog("Invalid state sensor reading response size: ${size}", ("size", rsp_str.size()));
        return false;
    }

    const pldm_state_sensor_reading_rsp_t* rsp = reinterpret_cast<const pldm_state_sensor_reading_rsp_t*>(rsp_str.data());
    
    // Validate composite sensor count
    if (rsp->composite_sensor_count == 0 || rsp->composite_sensor_count > 8) {
        ilog("Invalid composite sensor count: ${count}", ("count", rsp->composite_sensor_count));
        return false;
    }

    // Calculate expected response size 
    size_t expected_size = sizeof(pldm_state_sensor_reading_rsp_t) + (rsp->composite_sensor_count * sizeof(pldm_statesensor_field_t));
    if (rsp_str.size() < expected_size) {
        ilog("Response size too small for composite sensor count: ${size} < ${expected}", 
             ("size", rsp_str.size())("expected", expected_size));
        return false;
    }

    // Clear and resize output vector
    state_fields.clear();
    state_fields.reserve(rsp->composite_sensor_count);

    // Parse state fields for each composite sensor
    const uint8_t* state_data = reinterpret_cast<const uint8_t*>(rsp_str.data() + sizeof(pldm_state_sensor_reading_rsp_t));
    
    for (uint8_t i = 0; i < rsp->composite_sensor_count; ++i) {
        size_t offset = i * 4;
        
        pldm_statesensor_field_t field;
        field.operational_state = state_data[offset];
        field.present_state = state_data[offset + 1];
        field.previous_state = state_data[offset + 2];
        field.event_state = state_data[offset + 3];
        
        state_fields.push_back(field);
    }

    return true;
}

} // namespace dev
